#pragma once

#include "mem.h"

namespace sfc::ptr {

template <class T>
struct Unique {
  T* _ptr;

  [[sfc_inline]] Unique() noexcept : _ptr{nullptr} {}

  [[sfc_inline]] Unique(T* p) noexcept : _ptr{p} {}

  [[sfc_inline]] Unique(Unique&& other) noexcept : _ptr{other._ptr} {
    other._ptr = nullptr;
  }

  [[sfc_inline]] auto as_ref() const -> const T& {
    return *_ptr;
  }

  [[sfc_inline]] auto as_mut() -> T& {
    return *_ptr;
  }

  [[sfc_inline]] auto is_null() const -> bool {
    return _ptr == nullptr;
  }

  [[sfc_inline]] auto operator*() const -> const T& {
    return *_ptr;
  }

  [[sfc_inline]] auto operator*() -> T& {
    return *_ptr;
  }

  [[sfc_inline]] auto operator->() const -> const T* {
    return _ptr;
  }

  [[sfc_inline]] auto operator->() -> T* {
    return _ptr;
  }

  [[sfc_inline]] auto into_raw() && -> T* {
    const auto res = _ptr;
    _ptr = nullptr;
    return res;
  }
};

template <class T = void>
[[sfc_inline]] constexpr auto null() -> T* {
  return nullptr;
}

template <class T, class... U>
[[sfc_inline]] void xnew(T* dst, U&&... args) {
  new (dst) T{static_cast<U&&>(args)...};
}

template <class T>
[[sfc_inline]] auto read(const T* src) -> T {
  return *src;
}

template <class T>
[[sfc_inline]] void write(T* dst, T val) {
  new (dst) T{static_cast<T&&>(val)};
}

template <class T>
[[sfc_inline]] void write_unaligned(void* dst, const T& src) {
  __builtin_memcpy_inline(dst, &src, sizeof(T));
}

template <class T>
[[sfc_inline]] void write_bytes(T* dst, u8 val, usize cnt) {
  __builtin_memset(dst, val, cnt * sizeof(T));
}

template <class T>
[[sfc_inline]] void copy(const T* src, T* dst, usize cnt) {
  if constexpr (__is_trivially_constructible(T, T &&)) {
    __builtin_memcpy(dst, src, cnt * sizeof(T));
  } else {
    for (auto end = src + cnt; src != end; ++src, ++dst) {
      new (src) T{*dst};
    }
  }
}

template <class T>
[[sfc_inline]] void move(T* src, T* dst, usize cnt) {
  if constexpr (__is_trivially_constructible(T, T &&)) {
    __builtin_memmove(dst, src, cnt * sizeof(T));
  } else {
    if (dst < src) {
      for (const auto end = src + cnt; src != end; ++src, ++dst) {
        new (dst) T{static_cast<T&&>(*src)};
      }
    } else {
      src += cnt - 1;
      dst += cnt - 1;
      for (const auto end = src - cnt; src != end; --src, --dst) {
        new (dst) T{static_cast<T&&>(*src)};
      }
    }
  }
}

template <class T>
[[sfc_inline]] void fill(T* dst, T val, usize cnt) {
  if constexpr (sizeof(T) == 1) {
    __builtin_memset(dst, val, cnt);
  } else {
    for (const auto end = dst + cnt; dst != end; ++dst) {
      new (dst) T{val};
    }
  }
}

}  // namespace sfc::ptr
